/** Choose.java.

	Purpose:
		
	Description:
		
	History:
		12:48:31 PM Oct 22, 2014, Created by jumperchen

Copyright (C) 2014 Potix Corporation. All Rights Reserved.
 */
package org.zkoss.zuti.zul;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.HtmlShadowElement;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.sys.ShadowElementsCtrl;
import org.zkoss.zk.ui.util.Template;

/**
 * The <tt>choose</tt> tag works like a Java switch statement in that it lets you
 * choose between a number of alternatives. Where the switch statement has case
 * statements, the <tt>choose</tt> tag has <tt>when</tt> tags. A a switch statement
 * has default clause to specify a default action and similar way <tt>choose</tt>
 * has <tt>otherwise</tt> as default clause.
 * 
 * @author jumperchen
 * @since 8.0.0
 * @see When
 * @see Otherwise
 */
public class Choose extends TemplateBasedShadowElement {

	private static final long serialVersionUID = 2014102212483116L;
	private transient Otherwise _otherwise;
	private transient TemplateBasedShadowElement _evalTarget;

	// -- Component --//
	public void beforeChildAdded(Component child, Component refChild) {
		if (!(child instanceof When) && !(child instanceof Otherwise))
			throw new UiException("Unsupported child for <choose>: " + child);
		if (child instanceof Otherwise) {
			if (_otherwise != null && _otherwise != child)
				throw new UiException("Only one <otherwise> child for <choose>: " + child);
			if (refChild != null)
				throw new UiException("Last child of <choose> should be <otherwise>: " + child);
			_otherwise = (Otherwise) child;
		} else if (_otherwise != null) {
			if (_otherwise != refChild)
				throw new UiException("Last child of <choose> should be <otherwise>: " + child);

		}
		super.beforeChildAdded(child, refChild);
	}

	public void onChildRemoved(Component child) {
		if (child == _otherwise)
			_otherwise = null;
		super.onChildRemoved(child);
	}

	private void reEvaluate() {
		List<HtmlShadowElement> children = getChildren();
		TemplateBasedShadowElement oldTarget = _evalTarget;
		if (oldTarget != null) {
			oldTarget.getDistributedChildren().clear();
			oldTarget.getChildren().clear();
		}
		for (HtmlShadowElement shadow : children) {
			if (shadow instanceof When) {
				When when = ((When) shadow);
				if (when.isEffective()) {
					_evalTarget = when;
					Object shadowInfo = ShadowElementsCtrl.getCurrentInfo();
					try {
						ShadowElementsCtrl.setCurrentInfo(when);
						when.recreateDirectly();
						break;
					} finally {
						ShadowElementsCtrl.setCurrentInfo(shadowInfo);
					}
				}
			} else if (_otherwise != null) {
				_evalTarget = _otherwise;
				Object shadowInfo = ShadowElementsCtrl.getCurrentInfo();
				try {
					ShadowElementsCtrl.setCurrentInfo(_otherwise);
					_otherwise.recreateDirectly();
				} finally {
					ShadowElementsCtrl.setCurrentInfo(shadowInfo);
				}
			}
		}
	}

	protected void compose(Component host) {
		Template t = getTemplate("");
		if (t != null) {
			if (!isBindingReady()) {
				t.create(host, getNextInsertionComponentIfAny(), null, null);
			}

			reEvaluate();
		}
	}

	@SuppressWarnings("unchecked")
	protected Set<String> getDynamicKeys() {
		return Collections.EMPTY_SET;
	}

	public boolean isEffective() {
		return true;
	}

	// Choose, When, and Otherwise are a set, so they should be alive together.
	protected void rebuildSubShadowTree() {
		List<TemplateBasedShadowElement> children = getChildren();

		boolean dynamicValue = false;
		if (!children.isEmpty()) {
			for (HtmlShadowElement se : children) {
				if (se.isDynamicValue()) {
					dynamicValue = true;
					break;
				}
			}
			if (!dynamicValue) { // merge all grandchildren
				for (TemplateBasedShadowElement se : new ArrayList<TemplateBasedShadowElement>(children)) {
					se.rebuildSubShadowTree();
				}
			}
		}
		if (!dynamicValue && !isDynamicValue()) {
			mergeSubTree();
			detach();
		}
	}

	public boolean isDynamicValue() {
		if (hasSubBindingAnnotation())
			return true;
		return super.isDynamicValue();
	}

	public void detach() {
		super.detach();
		_evalTarget = null;
	}

	public void mergeSubTree() {

		// only merge evaluated target.
		if (_evalTarget != null) {
			List<HtmlShadowElement> children = _evalTarget.getChildren();
			if (children == null || children.isEmpty()) {
				return; // nothing to do
			}
			//mergeSubTree0(children);
			for (HtmlShadowElement child : new ArrayList<HtmlShadowElement>(children)) {
				Component previous = child.getPreviousInsertion();
				Component next = child.getNextInsertion();
				getParent().insertBefore(child, this);

				// resync the insertion of the child, if it has some comopnent sibling.
				if (previous != null && !(previous instanceof HtmlShadowElement)) {
					Component newPrevious = child.getPreviousInsertion();
					setPrevInsertion(previous, newPrevious);
					setPrevInsertion(child, previous);
				}
				if (next != null && !(next instanceof HtmlShadowElement)) {
					Component newNext = child.getNextInsertion();
					setPrevInsertion(newNext, next);
					setPrevInsertion(next, child);
				}

			}
		}
	}

	@SuppressWarnings("rawtypes")
	public Object clone() {
		final Choose clone = (Choose) super.clone();
		clone.afterUnmarshal();
		return clone;
	}

	@SuppressWarnings("rawtypes")
	private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
		s.defaultReadObject();
		afterUnmarshal();
	}

	public void recreate() {
		if (_afterComposed) { // execute after composed
			_afterComposed = false; // reset
			if (!isBindingReady()) {
				clearChildren();
			}
			afterCompose();
		}
	}

	private void afterUnmarshal() {
		for (Iterator<Component> it = getChildren().iterator(); it.hasNext();) {
			Component next = it.next();
			if (next instanceof Otherwise) {
				_otherwise = (Otherwise) next;
				break;
			}
		}
	}
}
